/**
 * Base64字符串处理
 * 支持一般Base64的编码和解码
 * 支持符合RFC_4648标准中"URL and Filename Safe Alphabet"的URL安全Base64编解码
 * 支持中文字符的编解码(Unicode编码)
 */
const lowers = "abcdefghijklmnopqrstuvwxyz";
const uppers = lowers.toUpperCase();
const numbers = "0123456789";
const others = "+/";
const urls = "-_";
const b64Map = `${uppers}${lowers}${numbers}${others}`.split("");
const urlMap = `${uppers}${lowers}${numbers}${urls}`.split("");

/**编码转成二进制(unicode转binary) */
const _toBinary = (ascii: number) => {
  const binary = [];
  while (ascii > 0) {
    const b = ascii % 2;
    ascii = Math.floor(ascii / 2);
    binary.push(b);
  }
  binary.reverse();
  return binary;
};

/**二进制编码转成十进制(binary转unicode) */
const _toDecimal = (binary: Array<number>) => {
  let dec = 0;
  let p = 0;
  for (var i = binary.length - 1; i >= 0; --i) {
    var b = binary[i];
    if (b == 1) dec += Math.pow(2, p);
    ++p;
  }
  return dec;
};

/**转成n字节UTF8二进制 */
const _toUTF8Binary = (n: number, binaryArray: Array<number>) => {
  const mustLen = 8 - (n + 1) + (n - 1) * 6;
  const fatLen = binaryArray.length;
  let diff = mustLen - fatLen;
  while (--diff >= 0) binaryArray.unshift(0);
  const binary: Array<number> = [];
  let _n = n;
  while (--_n >= 0) binary.push(1);
  binary.push(0);
  let i = 0;
  const len = 8 - (n + 1);
  for (; i < len; ++i) binary.push(binaryArray[i]);
  for (let j = 0; j < n - 1; ++j) {
    binary.push(1);
    binary.push(0);
    let sum = 6;
    while (--sum >= 0) binary.push(binaryArray[i++]);
  }
  return binary;
};
/**字符串转成二进制列表 */
const _toBinaryArray = (str: string) => {
  let binaryArray: Array<number> = [];
  for (let i = 0, len = str.length; i < len; ++i) {
    const unicode = str.charCodeAt(i);
    const _tmpBinary = _toBinary(unicode);
    if (unicode < 0x80) {
      let _tmpdiff = 8 - _tmpBinary.length;
      while (--_tmpdiff >= 0) _tmpBinary.unshift(0);
      binaryArray = binaryArray.concat(_tmpBinary);
    } else if (unicode >= 0x80 && unicode <= 0x7ff) {
      binaryArray = binaryArray.concat(_toUTF8Binary(2, _tmpBinary));
    } else if (unicode >= 0x800 && unicode <= 0xffff) {
      //UTF-8 3byte
      binaryArray = binaryArray.concat(_toUTF8Binary(3, _tmpBinary));
    } else if (unicode >= 0x10000 && unicode <= 0x1fffff) {
      //UTF-8 4byte
      binaryArray = binaryArray.concat(_toUTF8Binary(4, _tmpBinary));
    } else if (unicode >= 0x200000 && unicode <= 0x3ffffff) {
      //UTF-8 5byte
      binaryArray = binaryArray.concat(_toUTF8Binary(5, _tmpBinary));
    } else if (unicode >= 4000000 && unicode <= 0x7fffffff) {
      //UTF-8 6byte
      binaryArray = binaryArray.concat(_toUTF8Binary(6, _tmpBinary));
    }
  }
  return binaryArray;
};
/**二进制列表转成字符串 */
const _toUnicodeStr = (binaryArray: Array<number>) => {
  let unicode: number;
  let unicodeBinary: Array<number> = [];
  let str = "";
  for (let i = 0, len = binaryArray.length; i < len; ) {
    if (binaryArray[i] == 0) {
      unicode = _toDecimal(binaryArray.slice(i, i + 8));
      str += String.fromCharCode(unicode);
      i += 8;
    } else {
      let sum = 0;
      while (i < len) {
        if (binaryArray[i] != 1) break;
        ++sum;
        ++i;
      }
      unicodeBinary = unicodeBinary.concat(
        binaryArray.slice(i + 1, i + 8 - sum)
      );
      i += 8 - sum;
      while (sum > 1) {
        unicodeBinary = unicodeBinary.concat(binaryArray.slice(i + 2, i + 8));
        i += 8;
        --sum;
      }
      unicode = _toDecimal(unicodeBinary);
      str += String.fromCharCode(unicode);
      unicodeBinary = [];
    }
  }
  return str;
};
/**普通字符串编码成Base64字符串 */
const _encode = (str: string, isUrl: boolean = false) => {
  const base64_Index = [];
  const binaryArray = _toBinaryArray(str);
  const dictionary = isUrl ? urlMap : b64Map;
  let extra_Zero_Count = 0;
  for (let i = 0, len = binaryArray.length; i < len; i += 6) {
    const diff = i + 6 - len;
    if (diff == 2) {
      extra_Zero_Count = 2;
    } else if (diff == 4) {
      extra_Zero_Count = 4;
    }
    let _tmpExtra_Zero_Count = extra_Zero_Count;
    while (--_tmpExtra_Zero_Count >= 0) binaryArray.push(0);
    base64_Index.push(_toDecimal(binaryArray.slice(i, i + 6)));
  }
  let base64 = "";
  for (let i = 0, len = base64_Index.length; i < len; ++i) {
    base64 += dictionary[base64_Index[i]];
  }
  for (let i = 0, len = extra_Zero_Count / 2; i < len; ++i) {
    base64 += "=";
  }
  return base64;
};
/**Base64字符串解码成普通字符串 */
const _decode = (_base64Str: string, isUrl: boolean = false) => {
  const _len = _base64Str.length;
  let extra_Zero_Count = 0;
  const dictionary = isUrl ? urlMap : b64Map;

  if (_base64Str.charAt(_len - 1) == "=") {
    if (_base64Str.charAt(_len - 2) == "=") {
      extra_Zero_Count = 4; //两个等号说明补了4个0
      _base64Str = _base64Str.substring(0, _len - 2);
    } else {
      extra_Zero_Count = 2; //一个等号说明补了2个0
      _base64Str = _base64Str.substring(0, _len - 1);
    }
  }

  let binaryArray: Array<number> = [];
  for (let i = 0, len = _base64Str.length; i < len; ++i) {
    const c = _base64Str.charAt(i);
    for (let j = 0, size = dictionary.length; j < size; ++j) {
      if (c == dictionary[j]) {
        const _tmp = _toBinary(j);
        const _tmpLen = _tmp.length;
        /*不足6位的补0*/
        if (6 - _tmpLen > 0) {
          for (var k = 6 - _tmpLen; k > 0; --k) {
            _tmp.unshift(0);
          }
        }
        binaryArray = binaryArray.concat(_tmp);
        break;
      }
    }
  }
  if (extra_Zero_Count > 0) {
    binaryArray = binaryArray.slice(0, binaryArray.length - extra_Zero_Count);
  }
  return _toUnicodeStr(binaryArray);
};

/**Base64转换器 */
export default class MB64 {
  /**字符串编码成Base64 */
  public static encode(str: string) {
    return _encode(str, false);
  }
  /**Base64解码成字符串 */
  public static decode(str: string) {
    return _decode(str, false);
  }
  /**地址字符串编码成Base64 */
  public static urlEncode(url: string) {
    return _encode(url, true);
  }
  /**Base64解码成地址字符串 */
  public static urlDecode(url: string) {
    return _decode(url, true);
  }
}
